Skip to content

feat(notices): address a notice to a lineage conversation or root (#458) - #539

Merged
ScriptedAlchemy merged 4 commits into
mainfrom
feat/458-lineage-notices
Sep 4, 2026
Merged

feat(notices): address a notice to a lineage conversation or root (#458)#539
ScriptedAlchemy merged 4 commits into
mainfrom
feat/458-lineage-notices

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Closes #458.

Summary

A notice can now be addressed to one agent conversation or to a whole conversation tree, matched against the admitting request's request.lineage (#444: lineage is the identity surface; #391: no actor derivation).

  • AgentRecipient (packages/rsc-runtime/src/notices/contract.ts) gains two optional axes, additive to the existing four (every present axis must match):
    • conversation?: string — matches when request.lineage.conversation === conversation (exactly one agent thread: the root, or one subagent — Claude/Codex agent_id, Cursor child conversation_id).
    • root?: string — matches when request.lineage.root === root (the root conversation and every subagent under it; a pure match, so the publisher is included when it is under that root).
  • AgentNoticePrincipal gains an optional lineage?: Observed<AgentLineage>; runAgentRequest mounts it (agent-request.ts), so every generated surface — event routes, MCP tools (registry-correlated), routed CLI, rendered scripts, standalone hooks (resolveStandaloneLineage) — carries it. Unavailable lineage matches no conversation/root recipient (fail closed), exactly like an unavailable axis today. resources/subscribe records lineage: unavailable('not-provided') (no correlation window), so a subscriber never matches a lineage-addressed notice; GeneratedNoticePrincipal and the test harness's withContextIdentity carry the axis.
  • Ledger (state.ts, ledger.ts): recipientMatchesPrincipal checks the two axes; recordedNoticePrincipal projects the admission's principal to { conversation, root } so the journaled admitted payload never depends on the rest of the AgentLineage shape (relevant to Expose the lineage tree (siblings, children, live roots) to routes, not only the request's own chain #457, which may widen it). publish() rejects blank conversation/root with invalid-input.
  • Migration decision — no version bump. recipient.conversation, recipient.root, and principal.lineage on admitted are optional schema fields without defaults, so stored heads parse unchanged and the head-vs-replay check cannot diverge. An admission journaled before the axes carries no lineage and matches exactly the recipients it matched when written (no persisted notice names an axis it did not have), so the reducer does not change what any already-journaled event means — the state.ts v2 comment documents this. AGENT_NOTICE_STATE_VERSION stays 2; a test dispatches a pre-Let notices target a lineage conversation: recipient.conversation / recipient.root matched against request.lineage #458 admitted payload against the current definition.
  • examples/worktree-proximity: tool/before.tsx publishes recipient: { conversation: <other actor's conversation> } (noticeRecipientFor in event-support.ts); the derived worktree:<root> fallback actor names no conversation and keeps recipient.workspace.root. childFromLineage uses lineage.conversation as the actor id (identical to subagent.id on the example's targets, Claude and Codex). README updated.
  • Docs: generated notices reference page gains a "Recipient axes" section (en + zh, website/plugins/generated-reference.ts); runtime README notices section; docs/entry-conventions.md. Changeset: .changeset/458-lineage-notice-recipients.md (patch, additive).

Design question: a send(toConversation, notice) façade

Not added. The #99 host survey (2026-09-02) records that no pinned host offers a directed push channel (directed-push unavailable on claude/codex/cursor/portable), so any façade would be sugar over exactly what exists: publish() followed by admission on the recipient conversation's next event. The new axes make publish() expressive enough — { conversation } for one peer, { root } for the tree, conjoined with workspace/session when wanted. Implementing this did not change my view: the example needed one line (recipient), and a façade would have had to invent a delivery claim the ledger deliberately does not make (attempted stays the strongest cross-actor state). If an app wants send(), it is a two-line app-level helper.

Notes for reviewers

  • Cross-process journeys suite (packages/agent-bundle/tests/worktree-proximity-journeys.test.ts): the fixture previously omitted agent_id from the children's tool hook payloads and had no spawning Agent PreToolUse, so the lineage registry could not place the children (id-not-resolvable) and a conversation-addressed notice had nothing to match. Real Claude does both (127 hook payloads in the docs(lineage): live Claude Code 2.1.259 orchestration capture (parallel, sequential, nested spawns; resumed turns; /compact) #455 capture fixtures/host-lineage/claude-2.1.259-orchestration.ndjson), so the suite now runs the root's Agent PreToolUse before each SubagentStart and puts agent_id on the children's tool payloads. Consequences: child provenance becomes registry (placed by the runtime), and the suite additionally proves that an event the runtime cannot place under agent-a is not delivered to. Workspace-directed delivery (the derived-actor fallback) is proven in the example's route-unit suite instead.
  • The issue's acceptance line "two actors bound to the same worktree" is covered as: a conversation-addressed notice is not admitted on a sibling (agent-c) working in the recipient's worktree, nor on an unresolved-lineage event there, and is admitted on the addressed agent's event. The domain only flags cross-worktree overlap, so the conflict itself is still produced across worktrees.
  • Coordination with Expose the lineage tree (siblings, children, live roots) to routes, not only the request's own chain #457/Pass request identity, lineage, and read-only state/notices handles to context providers #459 (feat/457-lineage-tree): no lineage/* files touched; the ledger stores a projection of the lineage, so widening AgentLineage there does not affect journaled payloads.

Tests

  • packages/rsc-runtime/tests/notices-ledger.test.ts — new lineage-addressed recipients (#458) block: per-axis match/no-match matrix and conjunctions; unavailable lineage never matches; legacy principal without lineage; recorded-principal projection; conversation-addressed admission skips sibling/root/unresolved and lands on the addressed conversation; root-addressed admission reaches every member of the subtree and nothing outside; inbox and acknowledge scoped by conversation; blank axes rejected; pre-Let notices target a lineage conversation: recipient.conversation / recipient.root matched against request.lineage #458 admitted payload accepted under version 2.
  • packages/agent-bundle/tests/route-unit/lineage-notices.test.ts — replays the Claude 2.1.259 orchestration (docs(lineage): live Claude Code 2.1.259 orchestration capture (parallel, sequential, nested spawns; resumed turns; /compact) #455), Codex 0.147.0, and Cursor 3.18.25 captures through the lineage registry and renders event routes with those lineages: parent → one child (conversation), sibling and root excluded; child → tree (root) reaches every member; unresolved lineage never admits.
  • examples/worktree-proximity/tests/route-unit/routes.test.ts — journeys 4–6 now assert recipient: { conversation: 'agent-a' }; new: one agent in a shared worktree; derived-actor workspace fallback.
  • packages/agent-bundle/tests/worktree-proximity-journeys.test.ts — see above.

Verification (re-run on the rebased head 710fd2552)

  • pnpm typecheck clean; pnpm lint 0 errors / 0 warnings
  • pnpm test:unit 3263 passed; pnpm test:route-unit 73 passed (incl. the new file)
  • packages/rsc-runtime: rstest tests/notices-*.test.ts 99 passed
  • examples/worktree-proximity: pnpm typecheck, pnpm test:routes 12 passed
  • rstest --config rstest.integration.config.ts packages/agent-bundle/tests/worktree-proximity-journeys.test.ts passed
  • pnpm docs:site:build clean (dead-link, anchor, image, language-parity checks)

Review status

  • Codex reviewed b26dd5ffe — one P1 thread on .changeset/458-lineage-notice-recipients.md: a required AgentNoticePrincipal.lineage would break four-axis openRequest() / subscribe() callers (TypeScript consumers stop compiling; JavaScript consumers reach recordedNoticePrincipal() and throw). Fixed in f787c4462 by the compatible option the thread offered: lineage is optional on AgentNoticePrincipal and GeneratedNoticePrincipal; absent reads as unavailable (nothing journaled, no conversation/root match, every other axis unchanged). New ledger tests cover a four-axis principal through recipientMatchesPrincipal, recordedNoticePrincipal, and openRequest() admission. Changeset stays patch (additive). Answered here rather than on-thread by instruction.
  • Last-reviewed head: b26dd5ffe. Unreviewed heads: f787c4462 (only the optional-lineage fix above plus its tests and wording), 710fd2552 (the same two commits rebased onto main at feat(test): mountTestState/withTestState — one mounted state across several renderRoute calls #525, resolving the example route-unit test onto mountTestState; no other change), 530997a16 (rebased onto main at ci: run required checks on merge_group for the merge queue #536), and 06f7754de (rebased onto main at ci: add a static-named Verify gate job for the required check #549 so the new required Verify gate context runs; the branch-protection strict rule requires an up-to-date head). No source change in either rebase. No fresh review arrived within the fallback window and no PR comments are posted from this lane by instruction, so this merges on green CI per the reviewer-quota fallback; a later thread on the merged PR is answered in a follow-up PR.

@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab25b96

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@agent-bundle/runtime Patch
agent-bundle Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T06:58:58.976472Z b26dd5f PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@539
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@539
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@539

commit: c05f9ca

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b26dd5ffec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,6 @@
---
"@agent-bundle/runtime": patch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mark the required lineage field as breaking

Publishing @agent-bundle/runtime as a patch is unsafe because this commit makes AgentNoticePrincipal.lineage required in the public API. Existing TypeScript consumers constructing the previous four-axis principal will stop compiling, while existing JavaScript consumers of AgentNoticeLedger.openRequest() can reach recordedNoticePrincipal() and throw when it reads principal.lineage.state. Declare the runtime change as minor, or preserve compatibility by making lineage optional and treating omission as unavailable.

AGENTS.md reference: AGENTS.md:L102-L103

Useful? React with 👍 / 👎.

recipient.conversation matches request.lineage.conversation exactly and
recipient.root matches every request whose lineage root is that id, in
conjunction with the existing actor/host/session/workspace axes. The notice
principal carries the request's lineage on every generated surface; the
ledger journals only { conversation, root } of the admitting lineage as an
additive optional field (no definition version bump).

examples/worktree-proximity addresses proximity notices to the other actor's
conversation instead of its worktree; the derived worktree:<root> actor keeps
the workspace axis.
… principals keep working

Absent lineage reads as unavailable: nothing journaled, no conversation/root
match, every other axis unchanged. Test covers a four-axis principal handed
straight to openRequest.
@ScriptedAlchemy
ScriptedAlchemy force-pushed the feat/458-lineage-notices branch from 530997a to 06f7754 Compare September 4, 2026 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Let notices target a lineage conversation: recipient.conversation / recipient.root matched against request.lineage

1 participant